/*
 * \brief  Generic implementation parts of the signaling framework
 * \author Norman Feske
 * \date   2008-09-16
 */

/*
 * Copyright (C) 2008-2009 Norman Feske
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#include <base/native_signal.h>

using namespace Genode;


/************************
 ** Signal transmitter **
 ************************/

Native_signal_transmitter::Native_signal_transmitter(Native_signal_receiver_capability receiver)
:
	Thread<2048>("signal-tx"),
	_signal_cnt(0),
	_wait_lock(Lock::LOCKED),
	_receiver(receiver, &_snd_msg, &_rcv_msg)
{
	/* start execution of delivery thread */
	start();
}


void Native_signal_transmitter::_deliver()
{
	/* only deliver when a fault handler is registered */
	if (!_receiver.ready_for_send())
		return;

	/* reset notification count for the next signal */
	int signal_cnt;
	{
		Lock::Guard lock_guard(_signal_cnt_lock);
		signal_cnt = _signal_cnt;
		_signal_cnt = 0;
	}

	/* make blocking call to receiver */
	try {
		if (signal_cnt)
			_receiver << signal_cnt << IPC_CALL;

	/* catch ipc errors */
	} catch (...) { }
}


void Native_signal_transmitter::entry()
{
	while (true) {

		/* wait for '_signal_cnt' to become non-zero */
		_wait_lock.lock();

		/* deliver '_signal_cnt' number of notifications */
		_deliver();
	}
}


void Native_signal_transmitter::receiver(Native_signal_receiver_capability receiver)
{
	_receiver = Ipc_client(receiver, &_snd_msg, &_rcv_msg);
}


void Native_signal_transmitter::submit()
{
	Lock::Guard lock_guard(_signal_cnt_lock);
	_signal_cnt++;

	/* wake up delivery thread */
	_wait_lock.unlock();
}


/*********************
 ** Signal receiver **
 *********************/

Native_signal_receiver::Native_signal_receiver(Cap_session *cap_session)
:
	Thread<2048>("signal-rx"),
	_cap_session(cap_session),
	_curr_signal(0, 0),
	_pending(0),
	_fetch_next_lock(Lock::LOCKED)
{
	/*
	 * The signal-receiver capability has to be created by
	 * instantiating a 'Ipc_server' object in the context of the
	 * receiving thread. Hence, we have to employ a little
	 * handshaking protocol between the constructor thread and the
	 * signal-receiver thread.
	 */

	/* init lock for handshake with the receiver thread */
	Lock startup_lock(Lock::LOCKED);
	_startup_lock = &startup_lock;

	/* start receiver thread */
	start();

	/* wait for the completed startup of the receiver thread */
	_startup_lock->lock();
}


void Native_signal_receiver::entry()
{
	/* create ipc server and the corresponding capability */
	Msgbuf<32> _snd_msg, _rcv_msg;
	Ipc_server srv(&_snd_msg, &_rcv_msg);

	/* pass new capability to the constructor thread */
	_cap = srv;

	/* handshake with the thread constructing the signal receiver */
	_startup_lock->unlock();

	/*
	 * The startup lock is a local variable of the constructor.
	 * It becomes invalid right after releasing the lock because
	 * the constructor thread leaves the scope of the lock variable.
	 */

	while (true) {

		/* receive signal and unblock a caller of 'wait_for_signal' */
		int signal_cnt = 0;
		srv >> IPC_REPLY_WAIT >> signal_cnt;
		_curr_signal = Native_signal(srv.badge(), signal_cnt);
		_pending = true;
		_signal_available.up();

		/*
		 * Wait until the current signal state gets picked up by a
		 * caller of the 'wait_for_signal' function.
		 */
		_fetch_next_lock.lock();
	}
}


Native_signal Native_signal_receiver::wait_for_signal()
{
	/* block until the receiver thread has received a signal */
	_signal_available.down();

	/*
	 * The '_signal_available' semaphore gets incremented by the
	 * receiver thread, which defers the reception of further
	 * signals until the completion of 'wait_for_signal' (it blocks
	 * on '_fetch_next_lock'). This way, multiple threads may call
	 * the 'wait_for_signal' function simultaneously, will get
	 * serialized by the '_signal_available' semaphore and,
	 * therefore, will receive distinct signals.
	 */

	/* receiver thread has received a new signal in '_curr_signal' */
	Native_signal result = _curr_signal;

	/* invalidate current signal */
	_pending = false;

	/* release receiver thread to pick up the next signal */
	_fetch_next_lock.unlock();

	/* return last received signal */
	return result;
}
